package com.sxpi.user.service.useramount;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sxpi.common.result.R;
import com.sxpi.user.dto.UserAmountDto;
import com.sxpi.user.mapper.UserAmountRecordMapper;
import com.sxpi.user.mapper.UserMapper;
import com.sxpi.user.pojo.User;
import com.sxpi.user.pojo.UserAmountRecord;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Author: 董伟豪
 * @Date: 2022/11/8 21:14
 */
@Service
@Slf4j
public class IUserAmountRecordRedisServiceImpl extends ServiceImpl<UserAmountRecordMapper, UserAmountRecord>
        implements IUserAmountRecordRedisService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 提现方法---redis
     * 锁来保证数据的原子性  事务保证数据的完整性
     * @param userAmountDto
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    // 基于Redis的分布式锁这里加上事务就会出现问题
    // @GlobalTransactional
    public R takeMoneyRedisLock(UserAmountDto userAmountDto) {
        // 提现的用户
        Long userId = userAmountDto.getUserId();
        // 提现的金额
        Double money = userAmountDto.getMoney();

        // A --- user_ammount_key --- get --- finally -- lock
        // B --- user_ammount_key --- get
        // C --- user_ammount_key --- get

        // 需要有一个Key 加上了userId之后缩小范围，只是操作当前用户的全部线程中，只有一个才会操作成功
        String key = userId + "user:ammount:key:" + userId;
        String value = System.nanoTime() + "_" + UUID.randomUUID();
        // 拿到一个服务实例
        ValueOperations<String, String> stringStringValueOperations = stringRedisTemplate.opsForValue();
        // 调用setnx进行获取锁，如果获取返回true，否则返回false  并且一定要给锁设置过期时间
        // set key value ex 20 xx
        Boolean isLock = stringStringValueOperations.setIfAbsent(key, value,20L,TimeUnit.SECONDS);
        // 上锁（不可重入）
        if(isLock) {
            try {
                // 无锁
                User user = userMapper.selectById(userId);
                // 判断用户提现的余额是否充足，如果充足就提现
                if (user != null && user.getAmount().doubleValue() - money > 0) {
                    // 如果余额充足，就开始扣减余额
                    User updateUser = new User();
                    updateUser.setId(userId);
                    updateUser.setAmount(new BigDecimal(user.getAmount().doubleValue() - money));
                    // 这行代码只是为了同步一些余额，避免再次查询数据库
                    user.setAmount(updateUser.getAmount());
                    // 更新用户余额
                    userMapper.updateById(updateUser);

                    // 同时插入提现记录信息保存到用户提现表中
                    UserAmountRecord userAmountRecord = new UserAmountRecord();
                    userAmountRecord.setUserId(userId);
                    userAmountRecord.setUsername(user.getUsername());
                    userAmountRecord.setAvatar(user.getAvatar());
                    userAmountRecord.setMoney(new BigDecimal(money));
                    // 保存用户提现记录
                    this.saveOrUpdate(userAmountRecord);

                    // 输出日志
                    log.info("当前提现的金额是：{}，用户的余额是：{}", money, user.getAmount());
                } else {
                    // 否则就返回账户余额不足
                    return R.error("账户不存在，或者余额不足!!!");
                }
                return R.ok();
            } catch (Exception ex) {
                return R.error("执行失败...." + ex.getMessage());
            } finally {

                // 避免死锁，不管是正常还是异常情况下，都需要在redis的加锁成功以后都要释放锁
                // --自己的锁只能删除自己的锁，不允许删除别人的锁--
                if(value.equalsIgnoreCase(stringStringValueOperations.get(key).toString())){
                    stringRedisTemplate.delete(key);
                }
            }
        }else{
            return R.error("别着急，等慢一点~");
        }
    }

}
